<?php

declare(strict_types=1);

namespace core;

use GdImage;
use Exception;

/**
 * 验证码类
 *
 * 生成带有渐变背景、干扰元素和波浪扭曲效果的验证码图片
 */
final class Captcha
{
    /** @var string 字符集，用于生成验证码 */
    private const CHARSET = 'abcdefghkmnprstuvwxyzABCDEFGHKMNPRSTUVWXYZ23456789';

    /** @var string 最终生成的验证码内容 */
    private string $code = '';

    /** @var GdImage|null 验证码图像资源 */
    private ?GdImage $image = null;

    /** @var float 波浪扭曲振幅 */
    private float $waveAmplitude = 3.0;

    /** @var float 波浪扭曲频率 */
    private float $waveFrequency = 15.0;

    private string $fontPath;

    /**
     * constructor
     *
     * @param int    $codeLength  验证码长度
     * @param int    $width       图片宽度
     * @param int    $height      图片高度
     * @param int    $fontSize    字体大小
     *
     * @throws Exception 字体文件不可读取时抛出
     */
    public function __construct(
        private int $codeLength  = 4,
        private int $width       = 140,
        private int $height      = 40,
        private int $fontSize    = 22
    ) {

        $this->fontPath = __DIR__ . '/fonts/heebo.ttf';

        if (!is_readable($this->fontPath)) {
            throw new Exception("字体文件无法读取: {$this->fontPath}");
        }
    }

    /**
     * 生成并输出验证码图片
     *
     * @return void
     */
    public function generateImage(): void
    {
        $this->createGradientBackground();
        $this->createCode();
        $this->addPreTextNoise();
        $this->drawText();
        $this->addPostTextNoise();
        $this->applyWaveDistortion();
        $this->outputImage();
    }

    /**
     * 获取验证码文本（统一转换为小写）
     *
     * @return string
     */
    public function getCode(): string
    {
        return strtolower($this->code);
    }

    /**
     * 创建渐变背景。
     *
     * @return void
     */
    private function createGradientBackground(): void
    {
        $this->image = imagecreatetruecolor($this->width, $this->height);
        if ($this->image === false) {
            throw new Exception("无法创建验证码图像资源");
        }

        $startColorR = mt_rand(200, 255);
        $startColorG = mt_rand(200, 255);
        $startColorB = mt_rand(200, 255);

        $endColorR = mt_rand(200, 255);
        $endColorG = mt_rand(200, 255);
        $endColorB = mt_rand(200, 255);

        for ($i = 0; $i < $this->height; $i++) {
            $r = (int)($startColorR + ($endColorR - $startColorR) * $i / $this->height);
            $g = (int)($startColorG + ($endColorG - $startColorG) * $i / $this->height);
            $b = (int)($startColorB + ($endColorB - $startColorB) * $i / $this->height);
            $color = imagecolorallocate($this->image, $r, $g, $b);
            imageline($this->image, 0, $i, $this->width, $i, $color);
        }
    }

    /**
     * 随机生成验证码字符串。
     *
     * @return void
     */
    private function createCode(): void
    {
        $charSetLength = strlen(self::CHARSET) - 1;
        for ($i = 0; $i < $this->codeLength; $i++) {
            $this->code .= self::CHARSET[mt_rand(0, $charSetLength)];
        }
    }

    /**
     * 绘制验证码文字。
     *
     * @return void
     */
    private function drawText(): void
    {
        $x_padding = $this->width / ($this->codeLength + 1);

        for ($i = 0; $i < $this->codeLength; $i++) {
            $fontColor = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
            $currentFontSize = $this->fontSize + mt_rand(-2, 2);
            $angle = mt_rand(-25, 25);

            $x = (int)($x_padding * ($i + 0.5) + mt_rand(-5, 5));
            $y = (int)($this->height * 0.7 + mt_rand(-5, 5));

            imagettftext(
                $this->image,
                $currentFontSize,
                $angle,
                $x,
                $y,
                $fontColor,
                $this->fontPath,
                $this->code[$i]
            );
        }
    }

    /**
     * 添加背景噪点（绘制在文字下方）。
     *
     * @return void
     */
    private function addPreTextNoise(): void
    {
        for ($i = 0; $i < 150; $i++) {
            $color = imagecolorallocate($this->image, mt_rand(150, 220), mt_rand(150, 220), mt_rand(150, 220));
            imagesetpixel($this->image, mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
        }
    }

    /**
     * 添加文字之上的干扰线条。
     *
     * @return void
     */
    private function addPostTextNoise(): void
    {
        for ($i = 0; $i < 5; $i++) {
            $color = imagecolorallocate($this->image, mt_rand(80, 180), mt_rand(80, 180), mt_rand(80, 180));
            imageline(
                $this->image,
                mt_rand(0, $this->width),
                mt_rand(0, $this->height),
                mt_rand(0, $this->width),
                mt_rand(0, $this->height),
                $color
            );
        }
    }

    /**
     * 应用波浪形扭曲效果。
     *
     * @return void
     */
    private function applyWaveDistortion(): void
    {
        $tempImage = imagecreatetruecolor($this->width, $this->height);
        imagefill($tempImage, 0, 0, imagecolorallocate($tempImage, 255, 255, 255));

        // 批量处理多个像素列
        $batchSize = 10; // 根据测试调整最佳批量大小
        for ($x = 0; $x < $this->width; $x += $batchSize) {
            $width = min($batchSize, $this->width - $x);
            $y_offset = (int)($this->waveAmplitude * sin($x / $this->waveFrequency));
            imagecopy($tempImage, $this->image, $x, 0, $x, $y_offset, $width, $this->height);
        }

        imagedestroy($this->image);
        $this->image = $tempImage;
    }

    /**
     * 输出最终 PNG 图片。
     *
     * @return void
     */
    private function outputImage(): void
    {
        header('Content-type: image/png');
        header('Cache-Control: no-cache, must-revalidate');
        header('Pragma: no-cache');
        ob_clean();
        imagepng($this->image);
    }

    /**
     * 销毁图像资源。
     */
    public function __destruct()
    {
        if ($this->image !== null) {
            imagedestroy($this->image);
        }
    }
}
